package com.gitee.chessboard.jdbc.parser;

import com.gitee.chessboard.jdbc.JdbcManager;
import com.gitee.chessboard.jdbc.convert.TypeConverter;
import com.gitee.chessboard.jdbc.convert.TypeConverterFactory;
import com.gitee.chessboard.jdbc.parser.annotations.Id;
import com.gitee.chessboard.utils.NameSchemeConvert;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 分析处理器
 *
 * @author 敲代码的旺财
 * @date 2021/11/18 15:13
 */
public class ParserHandler {
    /**
     * jdbc的管理器
     */
    private JdbcManager jdbcManager;

    /**
     * 对象
     */
    private Object obj;

    /**
     * 对象类型
     */
    private Class<?> clazz;

    /**
     * 对象名
     */
    private String objName;

    /**
     * 表名
     */
    private String tableName;

    /**
     * 表id
     */
    private String tableId;

    /**
     * 方法列表
     */
    private List<Method> methods;

    /**
     * 方法名和方法关系
     */
    private Map<String, Method> methodsNameAndMethod;

    /**
     * 属性列表
     */
    private List<Field> fields;


    /**
     * 属性名和属性关系
     */
    private Map<String, Field> fieldNameAndField;

    /**
     * 属性名和属性类型关系
     */
    private Map<String, Class<?>> fieldNameAndFieldType;

    /**
     * 属性名和属性值关系
     */
    private Map<String, Object> fieldNameAndFieldValue;

    /**
     * 表字段和属性名名关系
     */
    private Map<String, String> tableFieldNameAndFieldName;

    /**
     * 表字段名和表字段类型关系
     */
    private Map<String, String> tableFieldNameAndTableFieldType;


    public ParserHandler(Object obj, JdbcManager jdbcManager) {
        this.jdbcManager = jdbcManager;
        this.obj = obj;
        parser();
    }

    private void parser() {
        parserClazz();
        parserObjName();
        parserTableName();
        parserMethods();
        parserMethodName();
        parserFields();
        parserFieldName();
        parserTableFieldName();
        parserFieldType();
        parserFieldValue();
        parserTableFieldName();
        parserTableFieldType();
    }

    private void parserClazz() {
        this.clazz = obj.getClass();
    }

    private void parserObjName() {
        this.objName = this.clazz.getSimpleName();
    }

    private void parserTableName() {
        this.tableName = NameSchemeConvert.pascal2UnderScore(this.objName);
    }

    private void parserMethods() {
        this.methods = new ArrayList<>();
        Class<?> tempClazz = this.clazz;
        while (tempClazz !=null && !tempClazz.getName().toLowerCase().equals("java.lang.object")) {
            this.methods.addAll(Arrays.asList(tempClazz.getDeclaredMethods()));
            //得到父类,然后赋给临时对象
            tempClazz = tempClazz.getSuperclass();
        }
        this.methods.forEach(m -> m.setAccessible(true));
    }

    private void parserMethodName() {
        this.methodsNameAndMethod = this.methods.stream().collect(Collectors.toMap(Method::getName, m -> m,(oldMethods,newMethods)->oldMethods));
    }

    private void parserFields() {
        this.fields = new ArrayList<>();
        Class<?> tempClazz = this.clazz;
        while (tempClazz !=null && !tempClazz.getName().toLowerCase().equals("java.lang.object")) {
            for (Field declaredField : tempClazz.getDeclaredFields()) {
                Class<?> fieldClazz = declaredField.getType();
                if (fieldClazz.isPrimitive()
                        || fieldClazz.isEnum()
                        || String.class.isAssignableFrom(fieldClazz)
                        || Date.class.isAssignableFrom(fieldClazz)
                        || Long.class.isAssignableFrom(fieldClazz)
                        || Integer.class.isAssignableFrom(fieldClazz)
                        || Boolean.class.isAssignableFrom(fieldClazz)
                        || Float.class.isAssignableFrom(fieldClazz)
                        || Double.class.isAssignableFrom(fieldClazz)
                        || BigDecimal.class.isAssignableFrom(fieldClazz)
                        || Character.class.isAssignableFrom(fieldClazz)) {
                    if (declaredField.getAnnotation(Id.class) != null){
                        this.tableId = NameSchemeConvert.camel2UnderScore(declaredField.getName());
                    }
                    this.fields.add(declaredField);
                }
            }
            //得到父类,然后赋给临时对象
            tempClazz = tempClazz.getSuperclass();
        }
        if (this.tableId == null || this.getTableId().length() == 0){
            throw new RuntimeException("无法查询到主键，请用Id注解标记主键");
        }
        this.fields.forEach(m -> m.setAccessible(true));
    }

    private void parserFieldName() {
        this.fieldNameAndField = this.fields.stream().collect(Collectors.toMap(Field::getName, m -> m,(oldMethods,newMethods)->oldMethods));
    }

    private void parserFieldType() {
        fieldNameAndFieldType = new HashMap<>();
        for (Field field : this.fields) {
            String fieldName = field.getName();
            Class<?> fieldClazz = field.getType();
            fieldNameAndFieldType.put(fieldName, fieldClazz);
        }
    }

    private void parserFieldValue() {
        fieldNameAndFieldValue = new HashMap<>();
        for (Field field : this.fields) {
            String fieldName = field.getName();
            String methodsName = "get" + NameSchemeConvert.camel2pascal(fieldName);
            Method method = this.methodsNameAndMethod.get(methodsName);
            Object fieldValue = null;
            if (method != null
                    && field.getType().equals(method.getReturnType())
                    && method.getParameterCount() == 0) {
                try {
                    fieldValue = method.invoke(this.obj);
                } catch (IllegalAccessException | InvocationTargetException e) {
                    //这里不抛出异常，尝试用反射获取值
                }
            }
            if (fieldValue == null) {
                try {
                    fieldValue = field.get(this.obj);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("无法解析到属性值", e);
                }
            }
            fieldNameAndFieldValue.put(fieldName, fieldValue);
        }
    }

    private void parserTableFieldName() {
        this.tableFieldNameAndFieldName = this.fields.stream()
                .collect(Collectors.toMap( m -> NameSchemeConvert.camel2UnderScore(m.getName()),Field::getName));
    }

    private void parserTableFieldType() {
        tableFieldNameAndTableFieldType = new HashMap<>();
        TypeConverterFactory typeConverterFactory = new TypeConverterFactory(jdbcManager);
        TypeConverter typeConverter = typeConverterFactory.get();
        for (Map.Entry<String, String> entry : this.tableFieldNameAndFieldName.entrySet()) {
            String tableFieldName = entry.getKey();
            String fieldName = entry.getValue();
            Field field = fieldNameAndField.get(fieldName);
            Class<?> fieldClazz = field.getType();
            String convert = typeConverter.convert(fieldClazz);
            if (convert != null && convert.length() != 0){
                tableFieldNameAndTableFieldType.put(tableFieldName, convert);
            }
        }
    }

    public Object getObj() {
        return obj;
    }

    public Class<?> getClazz() {
        return clazz;
    }

    public String getObjName() {
        return objName;
    }

    public String getTableName() {
        return tableName;
    }

    public List<Method> getMethods() {
        return methods;
    }

    public Map<String, Method> getMethodsNameAndMethod() {
        return methodsNameAndMethod;
    }

    public List<Field> getFields() {
        return fields;
    }

    public Map<String, Field> getFieldNameAndField() {
        return fieldNameAndField;
    }

    public Map<String, Class<?>> getFieldNameAndFieldType() {
        return fieldNameAndFieldType;
    }

    public Map<String, Object> getFieldNameAndFieldValue() {
        return fieldNameAndFieldValue;
    }

    public Map<String, String> getTableFieldNameAndFieldName() {
        return tableFieldNameAndFieldName;
    }

    public Map<String, String> getTableFieldNameAndTableFieldType() {
        return tableFieldNameAndTableFieldType;
    }

    public String getTableId() {
        return tableId;
    }
}
